﻿using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Tcp.StudyClient
{
    public partial class MainWindow : Window
    {
        // 客户端
        Socket client;

        // 是否检测服务器已关闭
        volatile bool IsCheckServerClose = false;

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 连接服务器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BtnConn_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                // 随机生成50000到60000-1,的随机数当做客户端的端口
                int port = new Random().Next(50000, 60000);
                // 绑定客户端自己的ip和地址(可省略)
                client.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port));
                await client.ConnectAsync(txtIP.Text, int.Parse(txtPort.Text));

                AppendTxt($"连接服务器成功,我的端口号{port}", Colors.Green);

                BtnConn.Visibility = Visibility.Collapsed;
                BtnDisConn.Visibility = Visibility.Visible;

                IsCheckServerClose = false; // 刚连接成功，没有检测

                // 不停检测连接是否断开
                _ = Task.Run(async () =>
                {
                    while (true)
                    {
                        // client.Poll(1000, SelectMode.SelectRead) && client.Available == 0)
                        // 用来判断一个 Socket 连接是否已经关闭或出错的常用技巧
                        /**
                         * 1. client.Poll(1000, SelectMode.SelectRead)
                         *   Poll 方法：
                         *   Poll 是 Socket 类的一个方法，用于非阻塞地检查套接字的状态。
                         *   它不会真正去读取数据，而是“探测”一下当前 socket 是否有事件发生。
                         *   参数说明：
                         *   第一个参数 1000：表示等待时间（单位是微秒）。1000 微秒 = 1 毫秒。
                         *   注意：虽然单位是微秒，但实际精度取决于操作系统，1000 通常表示等待 1 毫秒。
                         *   第二个参数 SelectMode.SelectRead：表示我们关心的是是否有数据可读，包括：
                         *   有数据到达，可以调用 Receive 来读取。
                         *   连接被对方关闭（FIN 包）。
                         *   连接出错（RST 包）。
                         *   返回值：
                         *   如果返回 true，表示 socket 当前处于“可读”状态 —— 即有数据可读，或者连接关闭/出错。
                         *   如果返回 false，表示在 1 毫秒内没有可读事件。
                         *   2. client.Available == 0
                         *   Available 是 Socket 的一个属性，表示当前可以立即读取的数据字节数。
                         *   如果 Available > 0，说明有数据到达，可以读取。
                         *   如果 Available == 0，说明没有数据可读。
                         *   3. 组合起来理解
                         *   这句话的逻辑是：
                         *  “如果 socket 处于可读状态（Poll 返回 true），但没有实际数据可读（Available == 0）”
                         *   连接已经被对方正常关闭（优雅关闭）
                         *   当对端调用 socket.Close() 或 socket.Shutdown() 后发送了 FIN 包，本端的 socket 会进入“可读”状态（因为有一个关闭事件需要处理）。
                         *   但此时没有应用层数据可读，所以 Available == 0。
                         *   这种情况调用 Receive() 会立即返回 0，表示连接关闭。
                         *   ❌ 也可能是连接异常中断（如网络断开、对端崩溃），但操作系统没有发送 RST 包，而是通过 FIN 或探测机制发现连接失效。
                         *   典型用途：检测连接是否关闭
                         *   if (client.Poll(1000, SelectMode.SelectRead) && client.Available == 0)
{                        *   {
                         *      // 对方已关闭连接
                         *      Console.WriteLine("Connection closed by peer.");
                         *      client.Close();
                         *   }
                         */
                        _ = Dispatcher.Invoke(async () =>
                        {
                            #region 接收信息
                            if (client != null && client.Connected)
                            {
                                // 接收的字节数组
                                byte[] recBytes = new byte[client.Available];
                                int recLen = await client.ReceiveAsync(recBytes);
                                if (recLen > 0)
                                {
                                    string recStr = Encoding.UTF8.GetString(recBytes);
                                    AppendTxt($"接收信息:{recStr}", Colors.Blue);
                                }
                            }
                            #endregion

                            #region 判断断开
                            // 0、客户端自己断开/服务器断开的
                            if (client != null && !client.Connected && !IsCheckServerClose)
                            {
                                IsCheckServerClose = true; // 已经检测到了
                                AppendTxt($"连接断了", Colors.Red);
                                BtnConn.Visibility = Visibility.Visible;
                                BtnDisConn.Visibility = Visibility.Collapsed;
                                return;
                            }
                            // 1、服务器断开
                            if (client != null && client.Poll(1000, SelectMode.SelectRead) && client.Available == 0 && !IsCheckServerClose)
                            {
                                IsCheckServerClose = true; // 已经检测到了
                                AppendTxt($"服务器已关闭", Colors.Red);
                                BtnConn.Visibility = Visibility.Visible;
                                BtnDisConn.Visibility = Visibility.Collapsed;
                            }
                            #endregion

                        });

                        await Task.Delay(1000);
                    }
                });

            }
            catch (Exception ex)
            {
                BtnConn.Visibility = Visibility.Visible;
                BtnDisConn.Visibility = Visibility.Collapsed;
                AppendTxt($"连接服务器异常,{ex.Message}", Colors.Red);
            }
        }

        /// <summary>
        /// 断开
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BtnDisConn_Click(object sender, RoutedEventArgs e)
        {
            if (client != null && client.Connected)
            {
                // 断开
                // 这个的参数false,代表不允许这个socket对象被重复利用
                await client.DisconnectAsync(false);

                AppendTxt($"已断开服务器", Colors.Red);

                BtnConn.Visibility = Visibility.Visible;
                BtnDisConn.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// 给服务器端发送信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BtnSend_Click(object sender, RoutedEventArgs e)
        {
            if (client != null && client.Connected)
            {
                byte[] sendBytes = Encoding.UTF8.GetBytes(txtSendContent.Text);
                int len = await client.SendAsync(sendBytes);
                if (len > 0)
                {
                    AppendTxt($"发送信息:{txtSendContent.Text}", Colors.Black);
                }
            }
        }


        FlowDocument doc = new FlowDocument();
        /// <summary>
        /// 追加信息到richtextbox结尾
        /// </summary>
        /// <param name="txt"></param>
        /// <param name="txtColor">文字颜色</param>
        private void AppendTxt(string txt, Color txtColor)
        {
            var p = new Paragraph(); // Paragraph 类似于 html 的 P 标签
            var r = new Run($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {txt}"); // Run 是一个 Inline 的标签
            p.Inlines.Add(r);
            p.Foreground = new SolidColorBrush(txtColor);//设置字体颜色
            doc.Blocks.Add(p);
            txtShowContent.Document = doc;
            txtShowContent.ScrollToEnd();
        }


    }
}